﻿using System;
using System.Configuration;
using System.Collections.Generic;
using System.Web;
using System.Web.Caching;
using System.Collections;
using System.Reflection;
namespace MikeKappel.Com.CS
{
    public class HelperCache
    {
        private static partial class Config
        {
            public static Boolean EnableCaching
            {
                get
                {
                    Boolean reuslt = true;
                    return (!Boolean.TryParse(ConfigurationManager.AppSettings["EnableCaching"], out reuslt) || reuslt);
                }
            }
            public static String CacheClearing
            {
                get
                {
                    return ConfigurationManager.AppSettings["EnableCaching"];
                }
            }
            public static Int32 CachingTimeSpan
            {
                get
                {
                    Int32 result = 0;
                    return (Int32.TryParse(ConfigurationManager.AppSettings["CachingTimeSpan"], out result)) ? result : result;
                }
            }
            public static Int32 CachingShortTerm
            {
                get
                {
                    Int32 result = 0;
                    return (Int32.TryParse(ConfigurationManager.AppSettings["CachingShortTerm"], out result)) ? result : result;
                }
            }
        }
        public static Cache cache = HttpContext.Current.Cache;
        /// <summary>
        ///  Indicates weather caching is enabled.
        /// </summary>
        public static Boolean IsEnabled
        {
            get
            {
                return Config.EnableCaching;
            }
        }
        /// <summary>
        ///  Indicates weather caching is enabled.
        /// </summary>
        public static Boolean ClearingIsEnabled
        {
            get
            {
                return (Config.CacheClearing != "");
            }
        }
        /// <summary>
        ///  Date and time the next cache clear was performed.
        /// </summary>
        public static Nullable<DateTime> NextClearingTime
        {
            get
            {
                if (!IsEnabled || !ClearingIsEnabled) return null;
                Boolean WillClearToday = LastCleared.HasValue
                    && LastCleared.Value.Date == DateTime.Now.Date
                    && DateTime.Compare(DateTime.Now, Convert.ToDateTime(DateTime.Now.Date.ToString("MM/dd/yyyy") + " " + Config.CacheClearing)) < 0;
                return (WillClearToday) ?
                    DateTime.Parse(DateTime.Now.Date.ToString("MM/dd/yyyy") + " " + Config.CacheClearing) :
                    DateTime.Parse(DateTime.Now.AddDays(1).Date.ToString("MM/dd/yyyy") + " " + Config.CacheClearing);
            }
        }
        /// <summary>
        ///  Date and time the last cache clear was performed.
        /// </summary>
        public static Nullable<DateTime> LastCleared
        {
            get
            {
                if (!IsEnabled || !ClearingIsEnabled) return null;
                if (HttpContext.Current.Cache["CacheLastCleared"] == null) Clear();
                return (DateTime)HttpContext.Current.Cache["CacheLastCleared"];
            }
        }
        /// <summary>
        ///  If cache is due to be clear cleared this will clear it.
        /// </summary>
        public static void Check()
        {
            if (NextClearingTime.HasValue && NextClearingTime.Value < DateTime.Now) Clear();
        }
        /// <summary>
        /// Clears all items from cache
        /// </summary>
        public static void Clear()
        {
            if (IsEnabled)
            {
                IDictionaryEnumerator CacheEnum = HttpContext.Current.Cache.GetEnumerator();
                while (CacheEnum.MoveNext()) HttpContext.Current.Cache.Remove(CacheEnum.Key.ToString());
                HttpContext.Current.Cache["CacheLastCleared"] = DateTime.Now;
            }
        }
        public enum CachingTime
        {
            LongTermAbsolute,
            LongTermSliding,
            ShortTermSliding
        }
        /// <summary>
        /// Will add object to Cache if:
        /// 1. Debug mode is "ON" and "DebugCacheDisabled" session is not "True"
        /// 2. If caching is enabled
        /// 3. The object is not Null
        /// 4. The object is not already in Cache
        /// 5. The object is Cacheable
        /// </summary>
        /// <typeparam name="T">Type of Object</typeparam>
        /// <param name="key">Key to retrieve Object</param>
        /// <param name="obj">Object to store in Cache</param>
        /// <returns></returns>
        public static CacheAddResult Add(String key, Object obj, CachingTime SpecialCacheSetting)
        {
            if (HttpContext.Current.IsDebuggingEnabled && Helper.ParseTo<Boolean>(HttpContext.Current.Session["DebugCacheDisabled"]))
                return CacheAddResult.DebugDisabled;

            Check();
            TimeSpan CacheTimeSpan;
            if (
                   (
                        (!Config.EnableCaching || Config.CachingTimeSpan == 0)
                   &&
                        (SpecialCacheSetting == CachingTime.LongTermAbsolute || SpecialCacheSetting == CachingTime.LongTermSliding)
                   )
                   || (SpecialCacheSetting == CachingTime.ShortTermSliding && Config.CachingShortTerm == 0)
                )
            {
                return CacheAddResult.Disabled;
            }
            else if (obj == null)
            {
                return CacheAddResult.Null;
            }
            else if (HttpContext.Current.Cache[key] != null)
            {
                return CacheAddResult.Conflict;
            }
            else if (SpecialCacheSetting == CachingTime.ShortTermSliding)
            {
                CacheTimeSpan = TimeSpan.FromMinutes(Config.CachingShortTerm);
            }
            else
            {
                CacheTimeSpan = TimeSpan.FromHours(Config.CachingTimeSpan);
            }

            Object objToCache = CacheableClone(obj);
            if (objToCache == null)
                return CacheAddResult.Forbidden;

            if (SpecialCacheSetting == CachingTime.LongTermAbsolute)
            {
                cache.Add(key, objToCache, null, DateTime.Now.Add(CacheTimeSpan), System.Web.Caching.Cache.NoSlidingExpiration, CacheItemPriority.High, null);
            }
            else
            {
                cache.Add(key, objToCache, null, System.Web.Caching.Cache.NoAbsoluteExpiration, CacheTimeSpan, CacheItemPriority.High, null);
            }
            return CacheAddResult.OK;
        }
        /// <summary>
        /// Will add object to Cache if:
        /// 1. Debug mode is "ON" and "DebugCacheDisabled" session is not "True"
        /// 2. If caching is enabled
        /// 3. The object is not Null
        /// 4. The object is not already in Cache
        /// 5. The object is Cacheable
        /// </summary>
        /// <typeparam name="T">Type of Object</typeparam>
        /// <param name="key">Key to retrieve Object</param>
        /// <param name="obj">Object to store in Cache</param>
        /// <returns></returns>
        public static CacheAddResult Add(String key, Object obj)
        {
            return Add(key, obj, CachingTime.LongTermSliding);
        }
        /// <summary>
        /// Site cache method outcomes
        /// </summary>
        public enum CacheAddResult
        {
            /// <summary>
            /// Added to Cache
            /// </summary>
            OK,
            /// <summary>
            /// If caching is Disabled
            /// </summary>
            Disabled,
            /// <summary>
            /// Debug is Disabled
            /// </summary>
            DebugDisabled,
            /// <summary>
            /// The object to be added is Null
            /// </summary>
            Null,
            /// <summary>
            /// The key is not already in Cache
            /// </summary>
            Conflict,
            /// <summary>
            /// Object not allowed
            /// </summary>
            Forbidden

        }
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key"></param>
        /// <param name="obj"></param>
        /// <returns></returns>
        public static T Get<T>(String key)
        {
            if (cache[key] != null)
            {
                T result = (T)cache[key];

                return result;
            }
            else
            {
                return default(T);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private static Object CacheableClone(Object obj)
        {
            Object result = null;
            if (Attribute.IsDefined(obj.GetType(), typeof(CachingForbidden)))
                throw new Exception(obj.GetType().ToString() + " could not invoke CacheableClone in HelperCache");

            if (obj is List<ICloneable> && (obj as List<ICloneable>) != null)
            {
                Object newList = (obj as List<ICloneable>).FindAll(item => true);
                (newList as List<Object>).ForEach(item => item = CacheableClone(item));
            }
            else if (obj is ICloneable)
            {
                result = (obj as ICloneable).Clone();
                // Iterate through all the properties of the class.
                foreach (PropertyInfo pInfo in result.GetType().GetProperties())
                    if (Attribute.IsDefined(pInfo, typeof(CachingForbidden)))
                        pInfo.SetValue(result, null, null);
            }
            else
                result = obj;

            return result;

        }
        /// <summary>
        /// Indicates whether a Property or Class should be stored in cache.
        /// </summary>
        [AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = true)]
        public class CachingForbidden : System.Attribute
        {
            /// <summary>
            /// Use of his custom Attribute prevents the Helper Cache from caching Property or class it is attributed to.
            /// </summary>
            public CachingForbidden()
            {

            }
        }
    }
}